package ast;
import java.util.ArrayList;

public class ScopeCheckVisitor implements Visitor {

    SymbolTable currTable;
    ArrayList<String> visitedClass;
    
	public ScopeCheckVisitor (SymbolTable table) {
		this.currTable = table;
		currTable.resetNextChild();
		visitedClass = new ArrayList<String>();
	}

    public String visit (ActualParamList n) {
    	String methodParaType = "";
    	if (n.left != null){
    		methodParaType = (String)n.left.accept(this);
    	}
		if (n.right != null){
			methodParaType = methodParaType + " x " + n.right.accept(this);
		}
		return methodParaType;
	}

    public Object visit (AddNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1) && !"chrlist".equals(t1))|| (!"double".equals(t2) && !"int".equals(t2) && !"chrlist".equals(t2))){
    		System.out.println("Type error: ADDNODE nodes are not double or int or chrlist");
    		return null;
    	}
        if ("chrlist".equals(t1) && "chrlist".equals(t2)) {
            return ("chrlist");
        }
        else if (("double".equals(t1) ||  "int".equals(t1)) && ("double".equals(t2) ||  "int".equals(t2)))
        {
        	if ("int".equals(t1) && "int".equals(t2)){
        		return ("int");
        	}
        	else{
        		return ("double");
        	}
        }
        else
        {
    		System.out.println("Type error: ADDNODE nodes are not compatible");
    		return null;        	
        }
    }

    public Object visit (AndNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"bool".equals(t1)) || (!"bool".equals(t2))){
    		System.out.println("Type error: ANDNODE nodes are not boolean");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

    public Object visit (AssignmentNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if (t1 != null)
    	{
    		if (!(("double".equals(t1) && "int".equals(t2)) || (t1.equals(t2)))){
    			System.out.println("Type error: ASSIGNMENTNODE type mismatch error");
    		}
    	}
    	return null;
    }

    public Object visit (BasicTypeDecNode n) {
        String expressionType;
        ArrayList tableValueArray = (ArrayList) currTable.get(n.id);
        String[] tableValueTemp = currTable.splitExpression((String)tableValueArray.get(0), ":");
        String tableReturnType = currTable.getReturnType(tableValueTemp);
        
        if (n.type.equals(tableReturnType))
        {
        	if (n.right != null)
        	{
        		expressionType = (String)n.right.accept(this);
        		if (!(("double".equals(n.type) && "int".equals(expressionType)) || (n.type.equals(expressionType))))
        		{
        			System.out.println("Type error: BASICTYPEDECNODE variable " + n.id + " of type " + n.type +
        					" mismatch with declared type " + expressionType + "!");
        		}
        	}
    		return null;
    	}
        return null;
    }

    public Object visit (BodyNode n) {
    	if (n.left != null) {n.left.accept(this);}
		if (n.right != null) {n.right.accept(this);}
    	return null;
    }

    public Object visit (CallVarNode n) {
    	ArrayList returnValueArray;
    	String[] returnValueTemp;
    	String typeExpression;
    	String methodPara;

       	if (n.left != null) {
            String value = (String)n.left.accept(this);
            if (value == null)
            {
                return null;
            }
            else
            {
                if (currTable.scopeCheck(value))
                {
                	SymbolTable sTable = (SymbolTable) currTable.getKeyType(value);

                	if (sTable.scopeCheck(n.id + "()"))
                	{
                		returnValueArray = (ArrayList)sTable.getKeyType(n.id + "()");
                    	if (n.right == null){
                    		methodPara = "void";
                    	}
                    	else{
                    		methodPara = (String)n.right.accept(this);
                    	}

                		for (Object returnValue : returnValueArray)
                    	{
                    		returnValueTemp = sTable.splitExpression((String)returnValue, ":");
                    		typeExpression = sTable.getTypeExpression(returnValueTemp);
                    		if (typeExpression.equals(methodPara))
                    		{
                    			return sTable.getReturnType(returnValueTemp);
                    		}
                    	}
                		System.out.println("Method " + n.id + "() with parameters " + methodPara
                									+ " is not visible from class type " + value + "!");
                        return null;
                	}
                	else
                	{
                		System.out.println("Method " + n.id + "() is not visible from class type " + value + "!");
                		return null;
                	}
                }
                else
                {
                    System.out.println("Class type " + value + " has not been declared!");
                    return null;
                }
            }
        }
        else{
            if (currTable.scopeCheck(n.id + "()"))
            {
            	returnValueArray = (ArrayList)currTable.getKeyType(n.id + "()");
            	if (n.right == null){
            		methodPara = "void";
            	}
            	else{
            		methodPara = (String)n.right.accept(this);
            	}

            	for (Object returnValue : returnValueArray)
            	{
            		returnValueTemp = currTable.splitExpression((String)returnValue, ":");
            		typeExpression = currTable.getTypeExpression(returnValueTemp);
            		if (typeExpression.equals(methodPara))
            		{
            			return currTable.getReturnType(returnValueTemp);
            		}
            	}
                System.out.println("Method " + n.id + "() with parameters " + methodPara + " does not exist!");
                return null;
            }
            else
            {
                System.out.println("Method " + n.id + "() has not been declared!");
                return null;
            }
        }
    }

    public Object visit (ClassDecNode n) {
    	for (String visited : visitedClass)
    	{
    		if (visited.equals(n.id))
    		{
    			return null;
    		}
    	}
    	visitedClass.add(n.id);
    	currTable = currTable.traverse();
    	if (n.left != null) n.left.accept(this);
    	currTable = currTable.endScope();
    	return null;
    }

    public Object visit (ClassDefNode n) {
    	if (n.left != null) n.left.accept(this);
    	if (n.right != null) n.right.accept(this);
    	return null;
    }

    public Object visit (DivideNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2))){
    		System.out.println("Type error: DIVIDENODE nodes are not double or int");
    		return null;
    	}
    	else{
    		return ("double");
    	}
    }

    public Object visit (EqualNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1) && !"bool".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2) && !"bool".equals(t2))){
    		System.out.println("Type error: EQUALNODE nodes are not double or int");
    		return null;
    	}
    	if (("int".equals(t1) || "double".equals(t1)) && ("int".equals(t2) || "double".equals(t2))){
    		return ("bool");
    	}
    	else if ("bool".equals(t1) && "bool".equals(t2)){
    		return ("bool");
    	}
    	else{
    		System.out.println("Type error: EQUALNODE nodes are not compatible");
    		return null;
    	}
    }

    public Object visit (ExprNode n) {
        //n.left.accept(this);
    	//n.right.accept(this);
    	return null;
    }

    public Object visit (FalseNode n) {
        return "bool";
    }

    public Object visit (GreaterEqualNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2))){
    		System.out.println("Type error: GREATEQUALNODE nodes are not double or int");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

    public Object visit (GreaterNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2))){
    		System.out.println("Type error: GREATERNODE nodes are not double or int");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

    public Object visit (IfNode n) { //new scope
    	n.expr.accept(this);
    	currTable = currTable.traverse();
    	n.ifBody.accept(this);
    	currTable = currTable.endScope();
    	currTable = currTable.traverse();
    	n.elseBody.accept(this);
    	currTable = currTable.endScope();
    	return null;
    }

    public Object visit (InNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"chrlist".equals(t1)) || (!"chrlist".equals(t2))){
    		System.out.println("Type error: INNODE nodes are not chrlists");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

    public Object visit (InstVarDecNode n) {
    	n.right.accept(this);
    	return null;
    }

    public Object visit (IntNumNode n) {
    	return "int";
    }

    public Object visit(LengthNode n) {
    	String t1 = (String) n.left.accept(this);
    	if (!"chrlist".equals(t1)){
    		System.out.println("Type error: LENGTHNODE node is not a chrlist");
    		return null;
    	}
    	else{
    		return ("int");
    	}
    }

    public Object visit (LessEqualNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2))){
    		System.out.println("Type error: LESSEQUALNODE nodes are not double or int");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

    public Object visit (LessNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2))){
    		System.out.println("Type error: LESSNODE nodes are not double or int");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

	public Object visit (List n) {
		if (n.left != null) n.left.accept(this);
		if (n.right != null) {n.right.accept(this);}
		return null;
	}

    public Object visit (MainProgramNode n) {
    	currTable = currTable.traverse();
    	if (n.left != null) {n.left.accept(this);}
    	if (n.right != null) {n.right.accept(this);}
    	currTable = currTable.endScope();
    	return null;
    }

    public String visit (MethodBodyNode n) {
    	String returnType = null;
    	if (n.varDecList != null) {n.varDecList.accept(this);}
    	if (n.stmtList != null) {n.stmtList.accept(this);}
    	if (n.returnStmt != null) {
    		returnType = (String)n.returnStmt.accept(this);
    	}
    	return returnType;
    }

    public Object visit (MethodCallNode n) {
    	n.left.accept(this);
    	return null;
    }

    public Object visit (MethodDecNode n) {
    	currTable = currTable.traverse();
    	if (n.formalParam != null) {n.formalParam.accept(this);}
    	String returnType = (String)n.methodBody.accept(this);
    	if (!((returnType == null && "void".equals(n.type)) || (n.type.equals(returnType))))
    	{
    		System.out.println("Return type error: Method " + n.id + " return type " + n.type + 
    							" is different from actual return type " + returnType + "!");
    	}
    	currTable = currTable.endScope();
    	return null;
    }

    public Object visit (MinusNode n) {
    	String t1 = 	(String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2))){
    		System.out.println("Type error: MINUSNODE nodes are not double or int");
    		return null;
    	}
    	if ("int".equals(t1) && "int".equals(t2)){
    		return ("int");
    	}
    	else{
    		return ("double");
    	}
    }

    public Object visit (Node n) {
    	//n.left.accept(this);
    	//n.right.accept(this);
    	return null;
    }

    public Object visit (NotEqualNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1) && !"bool".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2) && !"bool".equals(t2))){
    		System.out.println("Type error: NOTEQUALNODE nodes are not double or int");
    		return null;
    	}
    	if (("int".equals(t1) || "double".equals(t1)) && ("int".equals(t2) || "double".equals(t2))){
    		return ("bool");
    	}
    	else if ("bool".equals(t1) && "bool".equals(t2)){
    		return ("bool");
    	}
    	else{
    		System.out.println("Type error: NOTEQUALNODE nodes are not compatible");
    		return null;
    	}
    }

    public Object visit (NotInNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"chrlist".equals(t1)) || (!"chrlist".equals(t2))){
    		System.out.println("Type error: NOTINNODE nodes are not chrlists");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

    public Object visit (NotNode n) {
    	String t1 = (String) n.left.accept(this);
    	if (!"bool".equals(t1)){
    		System.out.println("Type error: NOTNODE node is not boolean");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

    public Object visit (ObjTypeDecNode n) {
    	return null;
    }

    public Object visit (OrNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"bool".equals(t1)) || (!"bool".equals(t2))){
    		System.out.println("Type error: ORNODE nodes are not boolean");
    		return null;
    	}
    	else{
    		return ("bool");
    	}
    }

    public Object visit (ParamListNode n) {
    	if (n.left != null) {n.left.accept(this);}
    	if (n.right != null) {n.right.accept(this);}
        return null;
    }

    public Object visit (ParamNode n) {
        return null;
    }

    public Object visit (PowerNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2))){
    		System.out.println("Type error: POWERNODE nodes are not double or int");
    		return null;
    	}
    	if ("int".equals(t1) && "int".equals(t2)){
    		return ("int");
    	}
    	else{
    		return ("double");
    	}
    }

    public Object visit (ProgramNode n) {
    	if (n.left != null) {n.left.accept(this);}
    	n.right.accept(this);
    	return null;
    }

    public Object visit (RealNumNode n) {
    	return "double";
    }

    public Object visit (RepeatNode n) {//new scope
    	currTable = currTable.traverse();
    	n.left.accept(this);
    	currTable = currTable.endScope();
    	n.right.accept(this);
    	return null;
    }

    public String visit (ReturnStmtNode n) {
    	String returnType = null;
    	if (n.left != null) {
    		returnType = (String) n.left.accept(this); 
    	}
    	return returnType;
    }

    public Object visit (StmtNode n) {
    	//n.left.accept(this);
    	//n.right.accept(this);
    	return null;
    }

    public Object visit (StringNode n) {
    	return "chrlist";
    }

    public Object visit (TimesNode n) {
    	String t1 = (String) n.left.accept(this);
    	String t2 = (String) n.right.accept(this);
    	if ((!"double".equals(t1) && !"int".equals(t1)) || (!"double".equals(t2) && !"int".equals(t2))){
    		System.out.println("Type error: TIMESNODE nodes are not double or int");
    		return null;
    	}
    	if ("int".equals(t1) && "int".equals(t2)){
    		return ("int");
    	}
    	else{
    		return ("double");
    	}
    }

    public Object visit (TrueNode n) {
        return "bool";
    }

    public String visit (VarNode n) {
    	ArrayList returnValueArray;
    	String[] returnValueTemp;

    	if (n.left != null) {
            String value = (String)n.left.accept(this);
            if (value == null)
            {
                return null;
            }
            else
            {
                if (currTable.scopeCheck(value))
                {
                	SymbolTable sTable = (SymbolTable) currTable.getKeyType(value);
                	if (sTable.scopeCheck(n.id))
                	{
                		returnValueArray = (ArrayList)sTable.getKeyType(n.id);
                		returnValueTemp = currTable.splitExpression((String)returnValueArray.get(0), ":");
                		return currTable.getReturnType(returnValueTemp);
                	}
                	else
                	{
                		System.out.println(n.id + " is not visible from class type " + value + "!");
                		return null;
                	}
                }
                else
                {
                    System.out.println("Class type " + value + " has not been declared!");
                    return null;
                }
            }
        }
        else{
            if (currTable.scopeCheck(n.id))
            {
            	returnValueArray = (ArrayList)currTable.getKeyType(n.id);
                returnValueTemp = currTable.splitExpression((String)returnValueArray.get(0), ":");
                return currTable.getReturnType(returnValueTemp);
            }
            else
            {
                System.out.println(n.id + " has not been declared!");
                return null;
            }
        }
    }
}